/* C.Getwd: Get the full pathname of the CWD */

#include "kernel.h"
#include "swis.h"

#include <string.h>
#include <stdlib.h>

#include "utils.h"

#define ReadDirName	6
#define ReadDiskName	5
#define SetDir		0
#define GetFSName	33

#define BUF_LEN		255

/* Some useful chunks of code */

#define setdir(d) \
{ \
	regs.r[1] = (int)(d); \
	if ( _kernel_swi (OS_FSControl, &regs, &regs) ) \
	{ \
		free(cwd); \
		free(pwd); \
		return NULL; \
	} \
}

#define GIVE_UP \
{ \
	free(pwd); \
	free(cwd); \
	return NULL; \
}

static char *append_disk (char *);
static char *append_cwd (char *);
static char *stradd (char *, char *, char);

/* Returns CWD pathname. If size != 0, use the area size long at buf,
 * otherwise malloc() an area. Return value is pathname, or 0 on any
 * error.
 */

char *getcwd (int preserve_pwd, char **pwd_ptr)
{
        char *pwd;
        char *cwd;
	char buf[BUF_LEN];
	_kernel_osgbpb_block blk;
	_kernel_swi_regs regs;

	/* Start with a null string */
	if ((cwd = malloc(1)) == NULL)
		return NULL;

	cwd[0] = 0;

	/* If we can corrupt the pwd, everything is easy */
	if (!preserve_pwd)
	{
		cwd = append_cwd(cwd);
		if (cwd == NULL)
			return NULL;

		/* Reset the cwd */
		setdir(cwd);
		return cwd;
	}

	/* We need to save the pwd - start with a null string */
	if ((pwd = malloc(1)) == NULL)
	{
		free(cwd);
		return NULL;
	}
	pwd[0] = 0;

	/* Initialise the OS call buffers */
	regs.r[0] = SetDir;
        blk.dataptr = buf;

        for (;;)
        {
		/* First get the cwd name */
                if (_kernel_osgbpb(ReadDirName,0,&blk) == _kernel_ERROR)
			GIVE_UP;

		/* Terminate the string */
                buf [2 + buf[1]] = '\0';

		cwd = stradd (cwd, buf+2, '.');
		if (cwd == NULL)
		{
			free(pwd);
			return NULL;
		}

		if (buf[1] == 1 && buf[2] == '$')
		{
			/* Reached the end - add fs:disk */
			if ((cwd = append_disk(cwd)) == NULL)
			{
				free(pwd);
				return NULL;
			}

			/* Now get the remainder of the pwd */
			setdir(*pwd ? "\\.^" : "\\");
			if ((pwd = append_cwd(pwd)) == NULL)
			{
				free(cwd);
				return NULL;
			}

			/* And exit */
			break;
		}

		/* Now get the next part of the pwd. This is "\" if
		 * we have none yet (ie *pwd = 0), otherwise "\.^".
		 */

		setdir(*pwd ? "\\.^" : "\\");

                if (_kernel_osgbpb(ReadDirName,0,&blk) == _kernel_ERROR)
			GIVE_UP;

		/* Terminate the string */
                buf [2 + buf[1]] = '\0';

		pwd = stradd (pwd, buf+2, '.');
		if (pwd == NULL)
		{
			free(cwd);
			return NULL;
		}

		if (buf[1] == 1 && buf[2] == '$')
		{
			/* Reached the end - add fs:disk */
			if ((pwd = append_disk(pwd)) == NULL)
			{
				free(cwd);
				return NULL;
			}

			/* Now get the remainder of the cwd */
			setdir("\\.^");
			if ((cwd = append_cwd(cwd)) == NULL)
			{
				free(cwd);
				return NULL;
			}

			/* And exit */
			break;
		}

		/* Now go back to the cwd, and up one level */
		setdir("\\.^");
        }

	/* We are OK - restore the CWD and the PWD */
	setdir(pwd);
	setdir(cwd);

	if (pwd_ptr)
		*pwd_ptr = pwd;
	else
		free(pwd);

	return cwd;
}

static char *append_disk (char *s)
{
	char buf[BUF_LEN];
	_kernel_osgbpb_block blk;
        _kernel_swi_regs regs;

	blk.dataptr = buf;

	if (_kernel_osgbpb(ReadDiskName,0,&blk) == _kernel_ERROR)
	{
		free(s);
		return NULL;
	}

        buf [1 + buf[0]] = '\0';	/* Terminate the string */
        buf [0] = ':';			/* Add an initial colon */

	s = stradd (s, buf, '.');

	if (s == NULL)
		return NULL;

	/* Now, get the filing system name */

	regs.r[0] = GetFSName;			/* Convert number to name */
	regs.r[1] = _kernel_osargs(0,0,0);	/* Filing system number */
	regs.r[2] = (int)buf;			/* FS name buffer */
	regs.r[3] = BUF_LEN;			/* Length of buffer */

	if ( _kernel_swi (OS_FSControl, &regs, &regs) )
	{
		free(s);
		return NULL;
	}

	return stradd (s, buf, ':');
}

static char *append_cwd (char *s)
{
	char buf[BUF_LEN];
	_kernel_osgbpb_block blk;
        _kernel_swi_regs regs;

	/* Set up the registers for the OS_FSControl call to
	 * go up one level in the directory tree.
	 */

	regs.r[0] = SetDir;
	regs.r[1] = (int)"^";

	/* Set up the area for the current directory name */

	blk.dataptr = buf;

	/* Now, go up the tree, adding the current directory name
	 * at each level, until we reach the root ($)
	 */

	do {
		if ( _kernel_osgbpb (ReadDirName, 0, &blk) == _kernel_ERROR )
		{
			free(s);
			return NULL;
		}

		buf [2 + buf[1]] = '\0';	/* Terminate the string */

		s = stradd (s, buf+2, '.');

		if (s == NULL)
			return NULL;

		if ( _kernel_swi (OS_FSControl, &regs, &regs) )
		{
			free(s);
			return NULL;
		}

	} while (buf[1] != 1 || buf[2] != '$');

	/* Add the fs:disk name on, and then return */

	return append_disk(s);
}

/* Add s2 to string s1. The string s1 must be obtained from malloc().
 * The old s1 will be freed, and a new area of malloc()-ed memory is
 * returned. If s1 is not a null string, ch will be inserted between
 * s1 and s2.
 * If an error occurs, s1 is freed, and NULL is returned.
 */

static char *stradd (char *s1, char *s2, char ch)
{
	int newlen = strlen(s1) + strlen(s2) + (ch ? 2 : 1);
	char *new = malloc(newlen);
	char *old = s1;
	char *p = new;

	if (new == NULL)
	{
		free(s1);
		return NULL;
	}

	while (*s2)
		*p++ = *s2++;

	if (*s1)
		*p++ = ch;

	while (*old)
		*p++ = *old++;

	*p = 0;
	free (s1);

	return new;
}

/*--------------------------------------------------------------------------*/

#ifdef test

#include <stdio.h>

int main (int argc, char *argv[])
{
	char *p, *q;

	if ( argc != 1 )
	{
		printf("%s: No parameters\n", argv[0]);
		return 1;
	}

	if ((p = getcwd(1,&q)) == NULL)
	{
		printf("Error\n");
		return 1;
	}

	printf("cwd: %s\n", p);
	printf("pwd: %s\n", q);

	free(p);
	free(q);

	return 0;
}

#endif
